--[[
	game.lua
]]

--------------------------------------------------------------------------------------------------
--[[ Declare the Constants and Variables Needed in Game in Advance ]]
--------------------------------------------------------------------------------------------------

players = {}
difficultySelectPlayer = 0 -- Difficulty is being selected, or the index of the selected player

-- Reference level to be used for configuring things like the stats of weapons or monsters
monsterLevel = 1 -- Monster level
LEVEL_MIN = 1 -- Player's minimum level
LEVEL_MAX = 99 -- Player's maximum level
WEAPONLEVEL_MAX = 60 -- Weapon's maximum level
mapDifficulty = SignalToGame.difficulty0 -- Current map difficulty

-- Use different types of stats for weapons as well as monsters by multiplying the values shown below according to their levels.
LevelRatio = {
	1.000,
	1.050,
	1.103,
	1.158,
	1.216,
	1.276,
	1.340,
	1.407,
	1.477,
	1.551,
	1.629,
	1.710,
	1.796,
	1.886,
	1.980,
	2.079,
	2.183,
	2.292,
	2.407,
	2.527,
	2.653,
	2.786,
	2.925,
	3.072,
	3.225,
	3.386,
	3.556,
	3.733,
	3.920,
	4.116,
	4.322,
	4.538,
	4.765,
	5.003,
	5.253,
	5.516,
	5.792,
	6.081,
	6.385,
	6.705,
	7.040,
	7.392,
	7.762,
	8.150,
	8.557,
	8.985,
	9.434,
	9.906,
	10.401,
	10.921,
	11.467,
	12.041,
	12.643,
	13.275,
	13.939,
	14.636,
	15.367,
	16.136,
	16.943,
	17.790,
	18.679,
	19.613,
	20.594,
	21.623,
	22.705,
	23.840,
	25.032,
	26.283,
	27.598,
	28.978,
	30.426,
	31.948,
	33.545,
	35.222,
	36.984,
	38.833,
	40.774,
	42.813,
	44.954,
	47.201,
	49.561,
	52.040,
	54.641,
	57.374,
	60.242,
	63.254,
	66.417,
	69.738,
	73.225,
	76.886,
	80.730,
	84.767,
	89.005,
	93.455,
	98.128,
	103.035,
	108.186,
	113.596,
	119.276,
}

-- Probability for determining weapon grade
WeaponGradeProb = {
	1.0,
	0.2,
	0.05,
	0.005
}

-- Default damage per weapon grade
WeaponGradeDamage = {
	1.0,
	1.25,
	1.5,
	2.0
}

-- Random weapon damage (If it is 0.15, then create one at random between values 0.85 - 1.15)
WeaponRandomDamage = 0.15

-- Weapon damage per level
WeaponLevelDamage = 1.0

-- Save monster types to be used in game
MonsterTypes = {
	Game.MONSTERTYPE.NORMAL0,
	Game.MONSTERTYPE.NORMAL1,
	Game.MONSTERTYPE.NORMAL2,
	Game.MONSTERTYPE.NORMAL3,
	Game.MONSTERTYPE.NORMAL4,
	Game.MONSTERTYPE.NORMAL5,
	Game.MONSTERTYPE.NORMAL6,
	Game.MONSTERTYPE.RUNNER0,
	Game.MONSTERTYPE.RUNNER1,
	Game.MONSTERTYPE.RUNNER2,
	Game.MONSTERTYPE.RUNNER3,
	Game.MONSTERTYPE.RUNNER4,
	Game.MONSTERTYPE.RUNNER5,
	Game.MONSTERTYPE.RUNNER6,
	Game.MONSTERTYPE.HEAVY1,
	Game.MONSTERTYPE.HEAVY2,
	Game.MONSTERTYPE.A101AR,
	Game.MONSTERTYPE.A104RL,
}

-- Define monster grades to be used in game
MonsterGrade = {
	normal = 1,
	rare = 2,
	unique = 3,
	legend = 4,
	END = 4
}

-- Table to tally by group whether or not all monsters died
monsterGroupCnt = {}

-- Table to manage monster waves (functions identically to the monster spawner)
monsterWaveCnt = {}
monsterWavePosition = {}

-- Choose whether or not to process wave monsters
WaveFuncState = {
	enable = 1, -- All waves activated
	disable = 3, -- Wave not possible
}
monsterWaveFuncState = WaveFuncState.enable

-- Probability for determining monster grade
MonsterGradeProb = {
	1.0,
	0.3,
	0.1,
	0.01
}

-- Weapon drop rate per monster grade (maximum of 1.0)
WeaponDropProb = {
	0.035,
	0.08,
	0.08,
	1
}

-- Configuration values per monster level
MonsterLevelVar = {
	normal =	{hpMin = 30, hpMax = 60, armorMin = 5, armorMax = 30, damageMin = 10, damageMax = 20, coinMin = 1, coinMax = 5},
	runner =	{hpMin = 20, hpMax = 50, armorMin = 0, armorMax = 20, damageMin = 10, damageMax = 30, coinMin = 3, coinMax = 8},
	heavy =		{hpMin = 150, hpMax = 250, armorMin = 20, armorMax = 55, damageMin = 20, damageMax = 40, coinMin = 10, coinMax = 15},
	a101ar =	{hpMin = 150, hpMax = 250, armorMin = 30, armorMax = 50, damageMin = 4, damageMax = 4, coinMin = 20, coinMax = 25},
	a104rl =	{hpMin = 150, hpMax = 250, armorMin = 30, armorMax = 50, damageMin = 17, damageMax = 17, coinMin = 20, coinMax = 25},
	etc =		{hpMin = 20, hpMax = 50, armorMin = 0, armorMax = 20, damageMin = 10, damageMax = 35, coinMin = 3, coinMax = 8},
}

-- EXP value based on monster HP (exp == hp * MonsterExpMult)
MonsterExpMult = 0.6

-- Configure the maximum level and EXP required per level
PlayerRequireExp = {
	87,
	360,
	864,
	1584,
	2625,
	3996,
	5586,
	7680,
	10206,
	13200,
	17061,
	21168,
	25857,
	31752,
	38475,
	45312,
	53754,
	64152,
	74727,
	87600,
	100548,
	116160,
	133308,
	152064,
	174375,
	196716,
	223074,
	251664,
	285099,
	318600,
	357492,
	402432,
	447579,
	499392,
	554925,
	618192,
	685869,
	758100,
	839592,
	926400,
	1023729,
	1127196,
	1236981,
	1359072,
	1494450,
	1637784,
	1795917,
	1969920,
	2153697,
	2355000,
	2574990,
	2806752,
	3067428,
	3341736,
	3639075,
	3960768,
	4308174,
	4682688,
	5096184,
	5529600,
	5994531,
	6504048,
	7060851,
	7643136,
	8276775,
	8964648,
	9696240,
	10487232,
	11340702,
	12245100,
	13232625,
	14292288,
	15427455,
	16641564,
	17955000,
	19355376,
	20864151,
	22486464,
	24208839,
	26073600,
	28067958,
	30217656,
	32509191,
	34948368,
	37584450,
	40404348,
	43415784,
	46626624,
	50092404,
	53775900,
	57735132,
	61956480,
	66476214,
	71306520,
	76486875,
	82003968,
	87898878,
	94215240,
	100000000
}

--------------------------------------------------------------------------------------------------
--[[ User Defined Functions Related to Monsters ]]
--------------------------------------------------------------------------------------------------

-- Configure the stats of monsters by level
function SetMonsterAttribute(monster, grade)

	if monster == nil then
		return
	end

	monster.user.level = monsterLevel
	monster.user.grade = grade

	monster.applyKnockback = true -- Monster takes knockback
	monster.canJump = false -- Monster cannot jump
	monster.viewDistance = 12 -- Range of vision for detecting enemies

	local levelVar
	if Game.MONSTERTYPE.NORMAL0 <= monster.type and monster.type <= Game.MONSTERTYPE.NORMAL6 then
		levelVar = MonsterLevelVar.normal
	elseif Game.MONSTERTYPE.RUNNER0 <= monster.type and monster.type <= Game.MONSTERTYPE.RUNNER6 then
		levelVar = MonsterLevelVar.runner
	elseif Game.MONSTERTYPE.HEAVY1 <= monster.type and monster.type <= Game.MONSTERTYPE.HEAVY2 then
		levelVar = MonsterLevelVar.heavy
	elseif monster.type == Game.MONSTERTYPE.A101AR then
		levelVar = MonsterLevelVar.a101ar
	elseif monster.type == Game.MONSTERTYPE.A104RL then
		levelVar = MonsterLevelVar.a104rl
	else
		levelVar = MonsterLevelVar.etc
	end

	-- Damage value is less steep compared to other values
	local damageMult = ((LevelRatio[monsterLevel] - 1.0) * 0.8) + 1.0
	monster.damage = math.floor(Game.RandomInt(levelVar.damageMin, levelVar.damageMax) * damageMult)

	monster.health = math.floor(Game.RandomInt(levelVar.hpMin, levelVar.hpMax) * LevelRatio[monsterLevel])
	monster.coin = math.floor(Game.RandomInt(levelVar.coinMin, levelVar.coinMax) * LevelRatio[monsterLevel])
	monster.user.exp = math.floor(monster.health * MonsterExpMult) -- EXP the player will receive for killing this particular monster

	-- Configure the color and stats based on the selected grade
	if grade == MonsterGrade.rare then
		monster:SetRenderFX(Game.RENDERFX.GLOWSHELL)
		monster:SetRenderColor({r = 0, g = 30, b = 255})
		monster.health = math.floor(monster.health * 3.0)
		monster.damage = monster.damage * 1.5
		monster.speed = 1.5
		monster.user.exp = math.floor(monster.user.exp * 1.5)
	elseif grade == MonsterGrade.unique then
		monster:SetRenderFX(Game.RENDERFX.GLOWSHELL)
		monster:SetRenderColor({r = 255, g = 30, b = 30})
		monster.health = math.floor(monster.health * 5.0)
		monster.damage = monster.damage * 2.0
		monster.speed = 1.5
		monster.user.exp = math.floor(monster.user.exp * 3.0)
	elseif grade == MonsterGrade.legend then
		monster:SetRenderFX(Game.RENDERFX.GLOWSHELL)
		monster:SetRenderColor({r = 255, g = 255, b = 100})
		monster.health = math.floor(monster.health * 12.0)
		monster.damage = monster.damage * 2.0
		monster.speed = 2.5
		monster.user.exp = math.floor(monster.user.exp * 8.0)
	end
end

-- Create a cluster of monsters at a specificed location
function CreateMonsters(type, num, pos, groupid, grade)

	if grade == nil then
		grade = MonsterGrade.normal
	end

	result = {}

	-- Create at total of num monsters
	for i = 1, num do
		monster = Game.Monster.Create(type, pos)
		if monster then
			-- Configure stats based on the monster grade
			SetMonsterAttribute(monster, grade)

			-- Save the monster's group number and incrementally increase the quantity by 1
			monster.user.groupid = groupid
			if monsterGroupCnt[groupid] then
				monsterGroupCnt[groupid] = monsterGroupCnt[groupid] + 1
			else
				monsterGroupCnt[groupid] = 1;
			end

			-- Save the resulting value
			table.insert(result, monster)
		end
	end

	return result
end

-- Function that occurs once all monsters in a cluster are dead
function OnMonsterKilled(monster)
    if monster.user.waveFunc then
		if monsterWaveFuncState == WaveFuncState.enable then
			monster.user.waveFunc(true, monster.user.waveFuncArg)
		else
			monster.user.waveFunc = nil
		end
    end

    -- Boss group is dead
    if monster.user.groupid == 8055 then
        monsterWaveFuncState = WaveFuncState.disable
        difficultySelectPlayer = 0
        Game.KillAllMonsters() -- Kill all monsters after the next OnUpdate
    end

    Game.SetTrigger('OnMonsterKilled' ..  monster.user.groupid, true)
end

-- Get rid of all monsters
function KillAllMonsters(callerOn)
	if callerOn == nil or callerOn == false then
		return
	end

	Game.KillAllMonsters()
end

--------------------------------------------------------------------------------------------------
--[[ User Defined Functions Related to Weapons ]]
--------------------------------------------------------------------------------------------------

-- Configure the minimum default stats for all weapons
function SetWeaponAttributeDefault(weapon)
	if weapon == nil then
		return
	end

	-- Unlimited ammo for secondary weapon
	if weapon:GetWeaponType() == Game.WEAPONTYPE.PISTOL then
		weapon.infiniteclip = true
	else
		weapon:AddClip1(3) -- Give 3 magazines by default
	end
	
	-- Default level
	if weapon.user.level == nil then
		weapon.user.level = Common.GetWeaponOption(weapon.weaponid).user.level
	end

	-- Default grade
	weapon.user.grade = WeaponGrade.normal
	weapon.color = Game.WEAPONCOLOR.WHITE
end

-- Configure weapon stats by level
function SetWeaponAttribute(weapon, level)

	if weapon == nil then
		return
	end

	-- Configure default stats
	SetWeaponAttributeDefault(weapon)

	-- Configure level
	weapon.user.level = level
	
	-- Call up the WeaponOption and grade of the current weapon
	local option = Common.GetWeaponOption(weapon.weaponid)
	local grade = option.user.grade

	-- Determine the probability to spawn by grade
	local weightMax = 0.0
	for i = grade, WeaponGrade.END do
		weightMax = weightMax + WeaponGradeProb[i]
	end

	local weight = Game.RandomFloat(0.0, weightMax)

	local weightSum = 0.0
	for i = grade, WeaponGrade.END do
		weightSum = weightSum + WeaponGradeProb[i]
		if weight <= weightSum  then
			grade = i
			break
		end
	end

	-- Calculate random damage by level and grade
	weapon.damage = WeaponLevelDamage * LevelRatio[level]
	weapon.damage = weapon.damage * (WeaponGradeDamage[grade] + Game.RandomFloat(-WeaponRandomDamage, WeaponRandomDamage))
	
	-- Maximum quantity of random stats
	local minAttrNum = 0
	local maxAttrNum = 0

	-- Configure the color and stat values based on the selected grade
	if grade == WeaponGrade.normal then
		weapon.color = Game.WEAPONCOLOR.WHITE
		minAttrNum = 0
		maxAttrNum = 0
	elseif grade == WeaponGrade.rare then
		weapon.color = Game.WEAPONCOLOR.BLUE
		minAttrNum = 0
		maxAttrNum = 1
	elseif grade == WeaponGrade.unique then
		weapon.color = Game.WEAPONCOLOR.RED
		minAttrNum = 1
		maxAttrNum = 2
	elseif grade == WeaponGrade.legend then
		weapon.color = Game.WEAPONCOLOR.ORANGE
		minAttrNum = 2
		maxAttrNum = 3
	end

	-- Prevents the occurrence of duplicate stats
	local attrDuplicateCheck = {}
	if weapon:GetWeaponType() == Game.WEAPONTYPE.PISTOL then
		attrDuplicateCheck[5] = true
	end

	-- Determines the stats
	local attrNum = Game.RandomInt(minAttrNum, maxAttrNum)

	local i = 0
	while i < attrNum do
		local attrType = Game.RandomInt(1, 5) -- 5 types, from speed to infiniteclip
		if attrDuplicateCheck[attrType] == nil then -- Repeats if stats are duplicates
			
			i = i + 1
			attrDuplicateCheck[attrType] = true

			if attrType == 1 then
				weapon.speed = Game.RandomFloat(1.2, 1.3)
			elseif attrType == 2 then
				weapon.knockback = Game.RandomFloat(1.2, 2.0)
				weapon.flinch = Game.RandomFloat(1.2, 2.0)
			elseif attrType == 3 then
				weapon.criticalrate = Game.RandomFloat(0.03, 0.2)
				weapon.criticaldamage = Game.RandomFloat(1.5, 2.5)
			elseif attrType == 4 then
				weapon.bloodsucking = Game.RandomFloat(0.01, 0.03)
			elseif attrType == 5 then
				weapon.infiniteclip = true
			end
		end
	end
end

-- Randomize weapon levels (based on monster levels)
function GetWeaponRandomLevel(level)

	local minLevel = level - 5
	local maxLevel = level + 3

	if minLevel < LEVEL_MIN then
		minLevel = LEVEL_MIN
	end
	if maxLevel > WEAPONLEVEL_MAX then
		maxLevel = WEAPONLEVEL_MAX
	end

	return Game.RandomInt(minLevel, maxLevel)
end

-- Create random weapons (based on level and position)
function CreateWeapon(level, pos)

	-- Randomize weapon types (based on weapon levels)
	local weightMax = 0.0
	local list = {}
	for i = 1, #WeaponList do
		local weaponOption = Common.GetWeaponOption(WeaponList[i])
		if weaponOption.user.level <= level then
			table.insert(list, weaponOption)
			weightMax = weightMax + (LevelRatio[weaponOption.user.level] * WeaponGradeProb[weaponOption.user.grade])
		end
	end

	local type = 0
	local weightSum = 0.0
	local weight = Game.RandomFloat(0.0, weightMax)
	for i = 1, #list do
		weightSum = weightSum + (LevelRatio[list[i].user.level] * WeaponGradeProb[list[i].user.grade])
		if weight <= weightSum  then
			type = list[i].weaponid
			break
		end
	end

	if type == 0 then
		return nil
	end

	local weapon = Game.Weapon.CreateAndDrop(type, pos)
	if weapon then
		SetWeaponAttribute(weapon, level)
	end

	return weapon
end

--------------------------------------------------------------------------------------------------
--[[ User Defined Functions Related to Player Levels]]
--------------------------------------------------------------------------------------------------

-- When a player levels up
function OnLevelUp(player)

	-- Increase HP
	player.maxhealth = math.floor(100 * LevelRatio[player.user.level])
	player.health = player.maxhealth

	-- Monster levels change based on players' levels in regular difficulty
	if mapDifficulty == SignalToGame.difficulty0 then
		if player.user.level > monsterLevel then
			monsterLevel = player.user.level
			if monsterLevel > 30 then
				monsterLevel = 30
			end
		end
	end

	-- Compare the WeaponOption and level to display the UI Lock indicator (shop window)
	for i = 1, #BuymenuWeaponList do
		local option = Common.GetWeaponOption(BuymenuWeaponList[i])
		if option then
			player:SetBuymenuLockedUI(BuymenuWeaponList[i], option.user.level > player.user.level, option.user.level)
		end
	end

	-- Compare the Weapon and level to display the UI lock indicator (weapon inventory)
	local invenWeapons = player:GetWeaponInvenList()
	for i = 1, #invenWeapons do
		player:SetWeaponInvenLockedUI(invenWeapons[i], invenWeapons[i].user.level > player.user.level, invenWeapons[i].user.level)
	end
end

-- Calculates the EXP ratio based on the player's current level and their EXP
function CalcExpRate(level, exp)
	return exp / PlayerRequireExp[level]
end

-- Distributes EXP to the player
function AddExp(player, exp)

	local pu = player.user

	-- Skips if the level is max
	if pu.level >= LEVEL_MAX then
		return
	end

	pu.exp = pu.exp + exp

	 -- Level up
	if pu.exp > PlayerRequireExp[pu.level] then
		pu.level = pu.level + 1

		if pu.level >= LEVEL_MAX then
			pu.exp = 0
		else
			pu.exp = pu.exp - PlayerRequireExp[pu.level - 1]
		end

		OnLevelUp(player)
	end

	-- Refresh after displaying the Level/EXP UI
	pu.expRate = CalcExpRate(pu.level, pu.exp)
	player:SetLevelUI(pu.level, pu.expRate)
end

--------------------------------------------------------------------------------------------------
--[[ Functions for Calling Studio (Regarding Weapons and Monsters) ]]
--------------------------------------------------------------------------------------------------

-- Function for splitting strings
function splitstr_tonumber(inputstr)
	local t = {}
	for str in string.gmatch(inputstr, "([^,]*)") do
		table.insert(t, tonumber(str))
	end
	return t
end

-- Define an AttackTo position for each monster group
MonsterAttackPos = {

	-- CreateDefaultMonsters
	[1] = {x = -12, y = 100, z = 1},
	[2] = {x = -9, y = 98, z = 1},
	[3] = {x = 14, y = 77, z = 1},
	[7] = {x = 80, y = 26, z = 1},
	[9] = {x = 81, y = 22, z = 1},
	[11] = {x = 81, y = 24, z = 1},
	[13] = {x = 36, y = -14, z = 1},
	[14] = {x = 35, y = -11, z = 1},
	[17] = {x = 34, y = -32, z = -3},
	[18] = {x = 35, y = -40, z = -3},
	[8055] = {x = 25, y = -31, z = -3},

	-- CreateWaveMonsters
	[1000] = {x = -4, y = 72, z = 1},

	-- CreateSpecialMonsters
	[10000] = {x = -12, y = 108, z = 1},
}

-- Determine monster grade
function GetRandomGrade(min, max)
	if min == max then
		return min
	end

	local grade = min
	
	local weightMax = 0.0
	for i = min, max do
		weightMax = weightMax + MonsterGradeProb[i]
	end

	local weight = Game.RandomFloat(0.0, weightMax)

	local weightSum = 0.0
	for i = min, max do
		weightSum = weightSum + MonsterGradeProb[i]
		if weight <= weightSum  then
			return i
		end
	end

	return min
end

function CreateDefaultMonsters(callerOn, arg)

	if callerOn == nil or callerOn == false then
		return
	end

	local args = splitstr_tonumber(arg) -- Divide the arg string into an array of numbers
	local type = MonsterTypes[args[1]] -- Monster type
	local num = args[2] -- Number of monsters to be summoned
	local groupid = args[3] -- Monster group id
	local grade = GetRandomGrade(args[4], args[5]) -- Monster grade

	-- Game.GetScriptCaller() : Calls the script function that called this function. Gets VoxelEntity.
	local monsters = CreateMonsters(type, num, Game.GetScriptCaller().position, groupid, grade)

	-- Monster groups that need their AttackTo position defined
	for i = 1, #monsters do
		if MonsterAttackPos[groupid] then
			monsters[i]:AttackTo(MonsterAttackPos[groupid]) -- Attack while moving to the defined coordinates
		end
	end

	-- Bosses are handled as exceptions
	if groupid == 8055 then
		for i = 1, #monsters do
			monsters[i].applyKnockback = false -- Removes knockback
		end
	end
end

-- Functions for creating monster waves
function CreateWaveMonsters(callerOn, arg)
	if callerOn == nil or callerOn == false then
		return
	end
	
	local args = splitstr_tonumber(arg)
	local type = MonsterTypes[args[1]]
	local num = args[2]
	local groupid = args[3]
	local grade = GetRandomGrade(args[4], args[5])
	local waveCnt = args[6] -- Number of times the waves were created

	if monsterWaveCnt[groupid] then
		monsterWaveCnt[groupid] = monsterWaveCnt[groupid] + 1
		if monsterWaveCnt[groupid] > waveCnt then
			if monsterWaveCnt[groupid] == waveCnt + 1 then
				Game.SetTrigger('OnWaveEnded' ..  groupid, true)
			end
				
			return
		end
	else
		monsterWaveCnt[groupid] = 1
	end
		
	if Game.GetScriptCaller() then
		monsterWavePosition[groupid] = Game.GetScriptCaller().position
	end

	local monsters = CreateMonsters(type, num, monsterWavePosition[groupid], groupid, grade)
	for i = 1, #monsters do
		monsters[i].user.waveFunc = CreateWaveMonsters
		monsters[i].user.waveFuncArg = arg

		if MonsterAttackPos[groupid] then
			monsters[i]:AttackTo(MonsterAttackPos[groupid]) -- Attack while moving to the defined coordinates
		end
	end
end

-- Function for creating monsters that drop certain items
function CreateSpecialMonsters(callerOn, arg)
	if callerOn then
	
		local args = splitstr_tonumber(arg)
		local type = MonsterTypes[args[1]]
		local num = args[2]
		local groupid = args[3]
		local grade = GetRandomGrade(args[4], args[5])

		local monsters = CreateMonsters(type, num, Game.GetScriptCaller().position, groupid, grade)
		for i = 1, #monsters do
			monsters[i].user.specialWeaponDrop = groupid -- Configure a special item to be dropped
			
			if MonsterAttackPos[groupid] then
				monsters[i]:AttackTo(MonsterAttackPos[groupid]) -- Attack while moving to the defined coordinates
			end
		end
	end
end

-- Function for configuring certain items to be dropped
function CreateSpecialWeapon(specialWeaponDrop, position)

	if specialWeaponDrop == 10000 then
		local weapon = Game.Weapon.CreateAndDrop(Common.WEAPON.P90, position)
		SetWeaponAttributeDefault(weapon)
	elseif specialWeaponDrop == 10001 then
		local weapon = Game.Weapon.CreateAndDrop(Common.WEAPON.M249, position)
		SetWeaponAttributeDefault(weapon)
	end
end

-- Create default weapons to be placed on the map
function CreateDefaultWeapon()
    local weapon = Game.Weapon.CreateAndDrop(Common.WEAPON.USP45, {x = -6, y = 150, z = 1})
	SetWeaponAttributeDefault(weapon)
end

-- Create random weapons
function CreateRandomWeapon(callerOn, arg)
	if callerOn then
		CreateWeapon(tonumber(arg), Game.GetScriptCaller().position)
	end
end

--------------------------------------------------------------------------------------------------
--[[ Event Functions ]]
--------------------------------------------------------------------------------------------------

-- Function called when the player spawns
function Game.Rule:OnPlayerSpawn(player)
	if player then
		player.armor = player.maxarmor -- Maximum armor
	end
end

-- Function called when the player first spawns after choosing a class
function Game.Rule:OnPlayerJoiningSpawn(player)

	-- Saves to player array
	players[player.index] = player

	-- Switches the camera to third-person view and the controls to the mouse
	player:SetThirdPersonFixedView(-45, 53, 100, 250) -- yaw, pitch, minDist, maxDist

	-- Modifies the calculation method for the mouse's ray-cast location from the ThirdPersonFixedView configuration
	player:SetThirdPersonFixedPlane(Game.THIRDPERSON_FIXED_PLANE.GROUND)

	-- Default level, EXP
	player.user.level = 1
	player.user.exp = 0
	player.user.expRate = 0
	
	-- Resets UI per level
	OnLevelUp(player) -- Resets the weapon lock UI
	player:SetLevelUI(player.user.level, player.user.expRate) -- Level/EXP UI configuration

	-- Team configuration
	player.team = Game.TEAM.CT
end

-- Function called when an entity (monster, player, etc.) takes damage
function Game.Rule:OnTakeDamage(victim, attacker, damage, weapontype, hitbox)
	if attacker == nil or victim == nil then return end

	if victim:IsMonster() then
		victim = victim:ToMonster()
		victim:ShowOverheadDamage(damage, 0) -- Displays damage above the head. Displays to all people when the second factor (the player index) is 0
	end
end

-- Function called when an entity is killed
function Game.Rule:OnKilled(victim, killer)
	if victim == nil or killer == nil then
		return
	end

	-- Revives the player when the player dies
	if victim:IsPlayer() then

		victim = victim:ToPlayer()

		-- Deducts 10% EXP if the difficulty is not regular difficulty
		if mapDifficulty ~= SignalToGame.difficulty0 then
			AddExp(victim, -math.floor(PlayerRequireExp[victim.user.level] / 10.0))
		end

		if victim.user.spawnable == true then
			victim:Respawn()
		end

		-- Hides the reload UI
		victim:Signal(SignalToUI.reloadFinished)
	end

	-- When the monster dies
	if victim:IsMonster() then

		victim = victim:ToMonster()

		-- Gives EXP if the killer is the player
		if killer:IsPlayer() then
			killer = killer:ToPlayer()
			AddExp(killer, victim.user.exp)
		end

		-- Drops a specified weapon for a monster or checks the weapon drop rate based on the monster grade before dropping a weapon
		if victim.user.specialWeaponDrop then
			CreateSpecialWeapon(victim.user.specialWeaponDrop, victim.position)
		else
			weight = Game.RandomFloat(0.0, 1.0)
			if weight <= WeaponDropProb[victim.user.grade] then
				CreateWeapon(GetWeaponRandomLevel(victim.user.level), victim.position)
			end
		end
		
		-- Checks the monster group quantity and delivers to the studio from OnMonsterKilled function if the value has become 0
		monsterGroupCnt[victim.user.groupid] = monsterGroupCnt[victim.user.groupid] - 1
		if monsterGroupCnt[victim.user.groupid] <= 0 then
			monsterGroupCnt[victim.user.groupid] = nil
			OnMonsterKilled(victim)
		end
	end
end

-- Function that inspects whether a weapon can be purchased or not
-- Weapons not included in the list of script weapons are not created
function Game.Rule:CanBuyWeapon(player, weaponid)
	local weaponOption = Common.GetWeaponOption(weaponid)
	return weaponOption.user.level <= player.user.level
end

-- Function that determines whether a weapon can be held in the hand or not
-- Weapons not included in the list of script weapons are not created
-- The weapon factor is delivered as nil if the Weapon class was never used
function Game.Rule:CanHaveWeaponInHand(player, weaponid, weapon)
	local weaponOptionCheck = Common.GetWeaponOption(weaponid).user.level <= player.user.level
	local weaponCheck = weapon == nil or weapon.user.level == nil or weapon.user.level <= player.user.level

	return weaponOptionCheck and weaponCheck
end

-- Function called upon obtaining a weapon
-- Weapons not included in the list of script weapons are not created
-- The weapon factor is delivered as nil if the Weapon class was never used
function Game.Rule:OnGetWeapon(player, weaponid, weapon)
	if weapon == nil then
		return
	end

	-- Configure default stats
	if weapon.user.level == nil then
		SetWeaponAttributeDefault(weapon)
	end

	-- Refresh the Lock UI
	player:SetWeaponInvenLockedUI(weapon, weapon.user.level > player.user.level, weapon.user.level)
end

-- Variable for delivering the reload time
reloadTimeSync = Game.SyncValue.Create("reloadTime")

-- Function called when reloading
-- The weapon factor is delivered as nil if the Weapon class was never used or if it's not a script weapon
function Game.Rule:OnReload(player, weapon, time)
	-- Displays the reload UI
	player:Signal(SignalToUI.reloadStarted)
	-- Delivers the reload time
	reloadTimeSync.value = time
end

-- Function called when reloading is complete
-- The weapon factor is delivered as nil if the Weapon class was never used or if it's not a script weapon
function Game.Rule:OnReloadFinished(player, weapon)
	player:Signal(SignalToUI.reloadFinished) -- Hides the reload UI
end

-- Function called after firing
-- The weapon factor is delivered as nil if the Weapon class was never used or if it's not a script weapon
function Game.Rule:PostFireWeapon(player, weapon, time)

	-- The reload UI is only displayed for weapons with a fire rate greater than 1 second
	if time > 1.0 then
		-- Displays the reload UI
		player:Signal(SignalToUI.reloadStarted)
		-- Delivers the reload time
		reloadTimeSync.value = time
	end
end

-- Function called when the player takes out a weapon
function Game.Rule:OnDeployWeapon(player, weapon)
	player:Signal(SignalToUI.reloadFinished) -- Hides the reload UI
end

-- Saves the 'save' data for each player
function Game.Rule:OnGameSave(player)
	if player == nil then
		return
	end
	
	-- Saves level, EXP
	player:SetGameSave('level', player.user.level)
	player:SetGameSave('exp', player.user.exp)

	-- Saves the level of the weapon currently held
	local primaryWeapon = player:GetPrimaryWeapon()
	local secondaryWeapon = player:GetSecondaryWeapon()

	if primaryWeapon then
		player:SetGameSave('wpn_lv_pri', primaryWeapon.user.level)
	else
		player:SetGameSave('wpn_lv_pri', 0)
	end
	
	if secondaryWeapon then
		player:SetGameSave('wpn_lv_sec', secondaryWeapon.user.level)
	else
		player:SetGameSave('wpn_lv_sec', 0)
	end

	-- Saves the level of the inventory weapon
	local invenWeapons = player:GetWeaponInvenList()
	for i = 1, #invenWeapons do
		player:SetGameSave('wpn_lv_inven' .. i, invenWeapons[i].user.level)
	end
end

function DoubleToInt(number)
	return math.floor(math.abs(number + EPSILON))
end

-- Loads the 'save' data for each player
function Game.Rule:OnLoadGameSave(player)
	if player == nil then
		return
	end

	-- Loads level, EXP
	player.user.level = DoubleToInt(player:GetGameSave('level'))
	player.user.exp = DoubleToInt(player:GetGameSave('exp'))

	if player.user.level == nil then
		player.user.level = 1
	end
	if player.user.exp == nil then
		player.user.exp = 0
	end

	player.user.expRate = CalcExpRate(player.user.level, player.user.exp)
	
	-- Loads the weapon currently held
	local primaryWeapon = player:GetPrimaryWeapon()
	local secondaryWeapon = player:GetSecondaryWeapon()

	if primaryWeapon then
		primaryWeapon.user.level = DoubleToInt(player:GetGameSave('wpn_lv_pri'))
	end
	
	if secondaryWeapon then
		secondaryWeapon.user.level = DoubleToInt(player:GetGameSave('wpn_lv_sec'))
	end
	
	-- Loads the level of the inventory weapon
	local invenWeapons = player:GetWeaponInvenList()
	for i = 1, #invenWeapons do
		invenWeapons[i].user.level = DoubleToInt(player:GetGameSave('wpn_lv_inven' .. i))
	end

	-- Resets UI per level
	OnLevelUp(player) -- Resets the weapon lock UI
	player:SetLevelUI(player.user.level, player.user.expRate) -- Level/EXP UI configuration
end

-- Resets the 'save' data for each player
function Game.Rule:OnClearGameSave(player)
	player.coin = 0
	player.user.level = 1
	player.user.exp = 0
	player.user.expRate = 0
	player:RemoveWeapon()
	player:ClearWeaponInven()

	-- Resets UI per level
	OnLevelUp(player) -- Resets the weapon lock UI
	player:SetLevelUI(player.user.level, player.user.expRate) -- Level/EXP UI configuration
end

--------------------------------------------------------------------------------------------------
--[[ Delivering Data Between the UI ]]
--------------------------------------------------------------------------------------------------

function Game.Rule:OnPlayerSignal(player, signal)

	if signal == SignalToGame.openWeaponInven then
		player:ToggleWeaponInven()
	end
end

-- Open shop (function to call studio)
function ShowBuymenu(callerOn)
	local triggerEnt = Game.GetTriggerEntity()
	if triggerEnt and triggerEnt:IsPlayer() then
		triggerEnt = triggerEnt:ToPlayer()
		triggerEnt:ShowBuymenu()
	end
end